package jp.co.nes.awssupport.business.utility;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

import jp.co.nes.awssupport.business.bean.BillingCsvBean;
import jp.co.nes.awssupport.business.constant.LineItemType;
import jp.co.nes.awssupport.business.constant.Term;
import jp.co.nes.awssupport.common.base.CommonException;
import jp.co.nes.awssupport.common.utility.DateUtil;

/**
 * BillingCsvReaderヘルパーです。
 */
public class BillingCsvReaderHelper {

    /** 請求CSVのタイムゾーン */
    private String csvZoneId;

    /** true: 解析した行がヘッダ行の場合, false: 解析した行がヘッダ行でない場合 */
    private boolean isHeader = true;

    /** true: 無効行, false: 有効行 */
    private boolean isInvalidLine = false;

    /** true: ファイルの1行目 false: ファイルの2行目以降 */
    private boolean isFirst = true;

    /** フィールド番号マップ(キー=フィールド名, 値=CSVに出力されている列の順番) */
    private Map<String, Integer> fieldNumberMap = new HashMap<>();

    /** 請求CSVのフィールド */
    private Field[] fields = BillingCsvBean.class.getDeclaredFields();

    /**
     * 請求CSVのタイムゾーンを引数にもつコンストラクタです<br>。
     * 請求CSVに設定する必要があるフィールドは、フィールド番号マップに必ずCSVに出力されている列の順番を設定する必要があります。
     *
     * @param fieldNumberMap フィールド番号マップ(キー=フィールド名, 値=CSVに出力されている列の順番)
     * @param csvZoneId 請求CSVのタイムゾーン
     */
    public BillingCsvReaderHelper(Map<String, Integer> fieldNumberMap, String csvZoneId) {
        this.fieldNumberMap = fieldNumberMap;
        this.csvZoneId = csvZoneId;
    }

    /**
     * isHeaderを返却します。
     *
     * @return true: 解析した行がヘッダ行の場合, false: 解析した行がヘッダ行でない場合
     */
    public boolean isHeader() {
        return isHeader;
    }

    /**
     * isInvalidLineを返却します。
     *
     * @return true: 無効行, false: 有効行
     */
    public boolean isInvalidLine() {
        return isInvalidLine;
    }

    /**
     * CSVファイルの行を解析します。
     *
     * @param line CSVファイルの行
     * @return 請求CSV
     */
    public BillingCsvBean parseLine(String line) {
        if (isFirst) {
            // ファイルの1行目の場合
            isHeader = true;
            isFirst = false;
            isInvalidLine = false;
            return null;
        }

        if (isHeader) {
            isHeader = false;
        }

        BillingCsvBean bean = parse(line);
        isInvalidLine = BillingCsvUtil.isInvalid(bean);
        return bean;
    }

    /**
     * CSVファイルの行を解析し、請求CSVを返却します。
     *
     * @param line CSVファイルの行
     * @return 請求CSV
     */
    private BillingCsvBean parse(String line) {
        List<String> tokenList = new ArrayList<>();
        String[] values = line.split(",");
        StringBuilder builder = new StringBuilder();
        boolean isQt = false; // true: 先端に"が存在するものが発生(末端に"が発生したとき、falseに戻る)
        for (String value : values) {
            if (value.length() > 0) {
                if (value.startsWith("\"")) {
                    // 先端に"が存在した場合
                    if (value.endsWith("\"")) {
                        // 末端に"が存在した場合
                        builder.append(value.substring(1, value.length() - 1));
                    } else {
                        // 末端に"が存在しなかった場合、isQtをtrue、先端の"を削除し、カンマを付加する。
                        isQt = true;
                        builder.append(value.substring(1));
                        builder.append(",");
                        continue;
                    }
                } else {
                    if (isQt) {
                        if (value.endsWith("\"")) {
                            // 末端に"が存在した場合
                            isQt = false;

                            // 末端の"を削除する。
                            builder.append(value.substring(0, value.length() - 1));
                        } else {
                            builder.append(value);
                            builder.append(",");
                            continue;
                        }
                    } else {
                        builder.append(value);
                    }
                }
            } else {
                builder.append(value);
            }
            tokenList.add(builder.toString());
            builder = new StringBuilder();
        }

        BillingCsvBean bean = new BillingCsvBean();
        for (int i = 0; i < fields.length; i++) {
            String fieldName = fields[i].getName();
            if (StringUtils.isEmpty(fieldName)) {
                // 通常発生しない。
                throw new CommonException("field name wrong");
            }
            if (!fieldNumberMap.containsKey(fieldName)) {
                // フィールド番号マップに設定されていないものは処理しない。
                continue;
            }

            Integer number = fieldNumberMap.get(fieldName);
            if (number < tokenList.size()) {
                String token = tokenList.get(number);
                if (StringUtils.isEmpty(token)) {
                    continue;
                }

                String methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
                Class<?> parameterType = fields[i].getType();
                try {
                    Method method = BillingCsvBean.class.getMethod(methodName, parameterType);
                    if (parameterType.isAssignableFrom(String.class)) {
                        method.invoke(bean, token);
                    } else if (parameterType.isAssignableFrom(LineItemType.class)) {
                        method.invoke(bean, BillingCsvUtil.toLineItemTypeFromBillingCsv(token));
                    } else if (parameterType.isAssignableFrom(ZonedDateTime.class)) {
                        method.invoke(bean, DateUtil.toZonedDateTime(token, "uuuu-MM-dd'T'HH:mm:ssX", csvZoneId));
                    } else if (parameterType.isAssignableFrom(BigDecimal.class)) {
                        method.invoke(bean, new BigDecimal(token));
                    } else if (parameterType.isAssignableFrom(Term.class)) {
                        method.invoke(bean, BillingCsvUtil.toTermFromBillingCsv(token));
                    } else {
                        // String, LineItemType, ZonedDateTime, BigDecimal, Term以外は未サポート(必要になった際に追加)。
                        throw new CommonException("parameterType wrong");
                    }

                } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
                        | InvocationTargetException e) {
                    throw new CommonException(e);
                }
            }
        }
        return bean;
    }
}
